package CalendarWS;

import com.google.gdata.client.GoogleAuthTokenFactory.UserToken;
import com.google.gdata.client.calendar.*;
import com.google.gdata.data.*;
import com.google.gdata.data.calendar.*;
import com.google.gdata.data.extensions.*;
import com.google.gdata.util.*;
import com.google.gdata.client.authn.oauth.*;

import java.net.*;
import java.io.*;
import java.sql.*;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.*;
import javax.servlet.http.*;

import sample.util.*;

/**
 * @author mike wang
 * Calendar manager facilitates communication between Google Calendar and SMO. 
 * Functions exists to make, update, get, and delete events from a Google cal.
 * Does not provide database connection.
 */
public class CalUpdater {
	// when authenticated, this will be interface to web
    private CalendarService _cs;
    private URL _urlDefaultPrivateFull;
    
	/**
	 * Constructor
	 */
	public CalUpdater() {
		_cs = new CalendarService("Calendar Module in SMO");
        try {
        	// URL to post events: private=visibility, full=projection
			_urlDefaultPrivateFull = new URL("https://www.google.com/calendar/feeds/default/private/full");
		} catch (MalformedURLException e) {
			e.printStackTrace();
		}
	} // end constructor
	
	/**
     * Setup Authentication for interaction with Google.
     * @param userName - Valid userName
     * @param password - Valid password
     */
    public void authenSetup(String userName, String password) {

    	// TODO: use OAuth
    	try { // tries to est. authentication
    		_cs.setUserCredentials(userName, password); 
    	} catch(AuthenticationException e){
    		System.out.println("authen error");
    	}
    	
    	UserToken authenToken = (UserToken) _cs.getAuthTokenFactory().getAuthToken();
//    	System.out.println(authenToken.getValue().toString());

    	try{ // writing out authentication token
	    	FileWriter fstream = new FileWriter("authenFile.txt");
	    	BufferedWriter out = new BufferedWriter(fstream);
	    	out.write(authenToken.getValue().toString());
	    	out.close();
    	} catch(Exception e) {
    		  System.out.println("error saving file ... " + e.getMessage());
    	}
    } // end method
    
    /**
     * Loads stored token for authentication.
     * @throws OAuthException 
     */
    public void authenTokenLoad() throws OAuthException {
    	GoogleOAuthParameters oauthParameters = new GoogleOAuthParameters();
    	  oauthParameters.setOAuthConsumerKey("754652786615.apps.googleusercontent.com");
    	  oauthParameters.setOAuthConsumerSecret("KIW3dS5X1O9cnWvFYocmRsUH");
    	  oauthParameters.setOAuthToken("1/LCG5xbq5yN0RXl-uBS5rB1UGMZc7KzBItYZv7B3FpRY");
    	  oauthParameters.setOAuthTokenSecret("t36FnwSq1Bdt-VIWtUG9oBZc");
    	  
    	  _cs.setOAuthCredentials(oauthParameters, new OAuthHmacSha1Signer());

		// TODO: use OAuth
//		String authenTokenRead = "";
//		try { // reading token from file
//				FileReader fstream = new FileReader("authenFile.txt");
//				BufferedReader read = new BufferedReader(fstream);
//				authenTokenRead = read.readLine();
//		} catch(Exception e){
//				System.out.println("error while reading file" + e.getMessage());
//		}
//		cs.setUserToken(authenTokenRead); // authenticates calendar-service obj
    } // end method


	/**
	 * Adds one, non-recurrent, event to Google Calendars for given start and end times.
	 * @param url - URL feed for posting new event
	 * @param title - Title of event object
	 * @param content - Content of event object
	 * @param startTime - Time of event start
	 * @param endTime - Time of event end
	 * @return Null if event unsuccessfully added, else returns the CalendarEventEntry object.
	 */
	public CalendarEventEntry addEvent(	String title, 
			String content,
			DateTime startTime,
			DateTime endTime) {
		CalendarEventEntry newEntry = new CalendarEventEntry();
		newEntry.setTitle(new PlainTextConstruct(title));
		newEntry.setContent(new PlainTextConstruct(content));
		
		When eventTimes = new When();
		eventTimes.setStartTime(startTime);
		eventTimes.setEndTime(endTime);
		newEntry.addTime(eventTimes);
		
		// event-transparency that is opaque will show time as BUSY
		newEntry.setTransparency(BaseEventEntry.Transparency.OPAQUE);
		
		CalendarEventEntry insertedEntry = null;
		try {
			insertedEntry = _cs.insert(_urlDefaultPrivateFull, newEntry);
		} catch (IOException e) {
			System.out.println("IO exeption within addEvent");
			e.printStackTrace();
		} catch (ServiceException e) {
			System.out.println("Service exception within addEvent");
			e.printStackTrace();
		}
		return insertedEntry;
	}
	/**
	 * Adds one, non-recurrent, event to Google Calendars given start time with
	 *   given duration in minutes and hours. 
	 * @param url - URL feed for posting new event
	 * @param title - Title of event object
	 * @param content - Content of event object
	 * @param startTime - Time of event start
	 * @param durationHours - Duration in hours
	 * @param durationMinutes - Duration in minutes
	 * @return Null if event unsuccessfully added, else returns the CalendarEventEntry object.
	 */
	public CalendarEventEntry addEvent(	String title, 
			String content,
			DateTime startTime,
			int durationHours,
			int durationMinutes) {
		CalendarEventEntry newEntry = new CalendarEventEntry();
		newEntry.setTitle(new PlainTextConstruct(title));
		newEntry.setContent(new PlainTextConstruct(content));
		
		When eventTimes = new When();
		eventTimes.setStartTime(startTime);
		DateTime endTime = new DateTime(startTime.getValue() + 1000*60*(durationHours*60 + durationMinutes));
		endTime.setTzShift(startTime.getTzShift());
		eventTimes.setEndTime(endTime);
		newEntry.addTime(eventTimes);
		
		// event-transparency that is opaque will show time as BUSY
		newEntry.setTransparency(BaseEventEntry.Transparency.OPAQUE);
		
		CalendarEventEntry insertedEntry = null;
		try {
			insertedEntry = _cs.insert(_urlDefaultPrivateFull, newEntry);
		} catch (IOException e) {
			System.out.println("IO exeption within addEvent");
			e.printStackTrace();
		} catch (ServiceException e) {
			System.out.println("Service exception within addEvent");
			e.printStackTrace();
		}
		return insertedEntry;
	} // end method
	
	/**
	 * Check if given start and end time conflict with existing start and end times.
	 * @param startTime - Start of time range to query for availability
	 * @param endTime - End of time range to query for availability
	 * @return True if no conflicting events found for given time range, else false.
	 * @throws ServiceException 
	 * @throws IOException 
	 */
	public Boolean availableToSchedule(	DateTime startTime,	
			DateTime endTime) throws IOException, ServiceException{
		// padding around query time to catch potential conflicts
		long padding = 1000*60*5; // 5 min. 
		startTime.setValue(startTime.getValue() + padding);
		endTime.setValue(endTime.getValue() + padding);
		// query for selected time range
		CalendarQuery q = new CalendarQuery(_urlDefaultPrivateFull);
		q.setMaxResults(99); // default 25 results returned 
		q.setMinimumStartTime(startTime); 
		q.setMaximumStartTime(endTime); 
		CalendarEventFeed resultFeed = _cs.query(q, CalendarEventFeed.class);
		
		// iterate through found events and look for conflicts
		for (int i = 0; i < resultFeed.getEntries().size(); ++i) {
			CalendarEventEntry entry = resultFeed.getEntries().get(i); // get 1 event entry
			DateTime existingStartTime = entry.getTimes().get(0).getStartTime(); 
			DateTime existingEndTime = entry.getTimes().get(0).getEndTime();
			// left.compareTo(right): 
			// 		-1 iff left  < right
			//  	 0 iff left == right
			//  	 1 iff left  > right
			
			System.out.println("\t Title: " + entry.getTitle().getPlainText());
			System.out.println("\t Start from: " + entry.getTimes().get(0).getStartTime().toString());
			System.out.println("\t Ends At: " + entry.getTimes().get(0).getEndTime().toString());
			
			// start times are equal
			if(existingStartTime.compareTo(startTime) == 0 &&
					entry.getTransparency().getValue().compareTo(BaseEventEntry.Transparency.OPAQUE_VALUE) == 0)
				return false;
			// new event starts in the middle of existing event
			if(existingStartTime.compareTo(startTime) < 0 &&
					existingEndTime.compareTo(startTime) > 0 &&
					entry.getTransparency().getValue().compareTo(BaseEventEntry.Transparency.OPAQUE_VALUE) == 0)
				return false;
			// new event ends in the middle of existing event (ok to end exactly at start of existing)
			if(endTime.compareTo(existingStartTime) > 0 &&
					endTime.compareTo(existingEndTime) < 0 &&
					entry.getTransparency().getValue().compareTo(BaseEventEntry.Transparency.OPAQUE_VALUE) == 0)
				return false;
			// new event starts before event but ends after event ends 
			if(startTime.compareTo(existingStartTime) < 0 &&
					endTime.compareTo(existingEndTime) > 0 &&
					entry.getTransparency().getValue().compareTo(BaseEventEntry.Transparency.OPAQUE_VALUE) == 0)
				return false;
		}
		return true; // if no conflict found
	} // end method
	
	/**
	 * Changes existing events.
	 * If event referenced cannot be found: do nothing.
	 * @param unique attribute(s) of EXISTING event
	 * @return True if event updated successfully
	 * @throws ServiceException 
	 * @throws IOException 
	 */
	public void updateEvent(EventChangeObject eventChg) throws IOException, ServiceException {
		DateTime newStart = DateTime.parseDateTime(eventChg.getStart()); // converting to ISO 8601
		DateTime newEnd = DateTime.parseDateTime(eventChg.getEnd());
		int tzoffset = -1 * eventChg.getTzoffset();
		newStart.setTzShift(tzoffset); 
		newEnd.setTzShift(tzoffset);
		
		long padding = 1000*60*5; // 5 min. 
		long timeDelta = -1 * 1000*(24*60*60*(long)eventChg.getDayDelta() + 60*(long)eventChg.getMinuteDelta());
		DateTime queryStart = new DateTime(newStart.getValue() + timeDelta - padding);
		queryStart.setTzShift(-1 * eventChg.getTzoffset());
		DateTime queryEnd = new DateTime(newEnd.getValue() + timeDelta + padding);
		queryEnd.setTzShift(-1 * eventChg.getTzoffset());
		
		CalendarQuery calendarQuery = new CalendarQuery(_urlDefaultPrivateFull);
		calendarQuery.setMaxResults(99); // default 25 results returned 
		calendarQuery.setMinimumStartTime(queryStart);
		calendarQuery.setMaximumStartTime(queryEnd);
		
		CalendarEventFeed resultFeed = _cs.query(calendarQuery, CalendarEventFeed.class);
		CalendarEventEntry googleEntry = null;
		
		System.out.println("number of entries returned: " + resultFeed.getEntries().size());
		for (int i = 0; i < resultFeed.getEntries().size(); ++i) {
			CalendarEventEntry entry = resultFeed.getEntries().get(i);
			System.out.println("title: " + entry.getTitle().getPlainText());
			if(entry.getIcalUID().substring(0, entry.getIcalUID().indexOf('@')).compareTo(eventChg.getId()) == 0){ // same event
				googleEntry = entry;
				break;
			}
		}
		
		// edit event on google
		if(googleEntry != null) {
			if(eventChg.getActionType().compareTo("time_change") == 0) { // change time only
//				When eventTimes = new When();
//				eventTimes.setStartTime(newStart);
//				eventTimes.setEndTime(newEnd);
//				googleEntry.addTime(eventTimes);
				googleEntry.getTimes().get(0).setStartTime(newStart);
				googleEntry.getTimes().get(0).setEndTime(newEnd);
				URL editUrl = new URL(googleEntry.getEditLink().getHref());
				
				System.out.println(newStart.toString());
				System.out.println(googleEntry.getTimes().get(0).getStartTime().toString());
				System.out.println(googleEntry.getEditLink().getHref());
				
				CalendarEventEntry updatedEntry = (CalendarEventEntry)_cs.update(editUrl, googleEntry);
				System.out.println(updatedEntry.getIcalUID());
				System.out.println(updatedEntry.getTimes().get(0).getStartTime().toString());
			}
		}
	} // end method
	
	/**
	 * remove the event
	 * @return retrieved event
	 * @throws ServiceException 
	 * @throws IOException 
	 */
	public boolean deleteEvent(EventObject eventObject) throws IOException, ServiceException {
		CalendarQuery calendarQuery = new CalendarQuery(_urlDefaultPrivateFull);
		calendarQuery.setMinimumStartTime(DateTime.parseDateTime(eventObject.getStart()));
		calendarQuery.setMaximumStartTime(DateTime.parseDateTime(eventObject.getEnd()));
		
		CalendarEventFeed resultFeed = _cs.query(calendarQuery, CalendarEventFeed.class);
		CalendarEventEntry googleEntry = null;
		for (int i = 0; i < resultFeed.getEntries().size(); ++i) { // search through returned event-list
			CalendarEventEntry entry = resultFeed.getEntries().get(i);
			if(entry.getIcalUID().substring(0, entry.getIcalUID().indexOf('@')).compareTo(eventObject.getId()) == 0){ // same event
				googleEntry = entry;
//				System.out.println("about to delete event w/ uid: " + entry.getIcalUID());
				googleEntry.delete();
				return true;
			}
		}
		return false; // no event found; delete not performed
	} // end method
	
	/**
	 * Retrieves events from Google and compares each to locally registered events. 
	 *  -if retrieved events do not exist in database, create them.
	 *  -if existing events in database do not exist on google, delete them.
	 * @return True if successful
	 */
	public Boolean syncEventsFromGoogle(){
		// taskinfo is table to get/set events
		return true;
	} // end method
} // end class
